﻿using System;
using System.IO.Ports;

namespace BTSegWay
{
    /// <summary>
    /// Handles communication with DIYS via Serial connection
    /// </summary>
    public class DIYSCom
    {

        #region private members
        private readonly SerialPort _sp;
        private const string EndLineChar = "\n";
        private readonly DIYSValues _diys;
        private bool _connectionStatus;
        #endregion

        #region properties
        /// <summary>
        /// Gets or Sets COM port name
        /// </summary>
        public string COMPort { get; set; }

        /// <summary>
        /// Gets or Sets baud rate
        /// </summary>
        public int BaudRate { get; set; }

        /// <summary>
        /// Gets or Sets parity
        /// </summary>
        public Parity Parity { get; set; }

        /// <summary>
        /// Gets or Sets data bits
        /// </summary>
        public int DataBits { get; set; }

        /// <summary>
        /// Gets or Sets stop bits
        /// </summary>
        public StopBits StopBits { get; set; }

        
        private bool Online 
        {
            get { return _connectionStatus; }
            set
            {
                _connectionStatus = value;
                if (OnConnectionStatusChanged != null)
                    OnConnectionStatusChanged(_connectionStatus);
            }
        }
        #endregion

        #region events
        /// <summary>
        /// Occurs when new data is received
        /// </summary>
        public event StringDelegate OnInputChanged;

        /// <summary>
        /// Occurs when connection status changes
        /// </summary>
        public event BooleanDelegate OnConnectionStatusChanged;
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="diys">DIYSValues</param>
        public DIYSCom(DIYSValues diys)
        {
            _diys = diys;

            _sp = new SerialPort();
            this.COMPort = "COM9";
            this.BaudRate = 9600;
            this.Parity = Parity.None;
            this.DataBits = 8;
            this.StopBits = StopBits.One;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="diys">DIYSValues</param>
        /// <param name="COM_port">Wanted COM port</param>
        /// <param name="baudRate">Wanted Baud rate</param>
        /// <param name="parity">Wanted Parity</param>
        /// <param name="dataBits">Wanted number of data bits</param>
        /// <param name="stopBits">Wanted stop bit</param>
        public DIYSCom(DIYSValues diys, string COM_port, int baudRate, Parity parity, int dataBits, StopBits stopBits)
        {
            _diys = diys;

            COMPort = COM_port;
            BaudRate = baudRate;
            this.Parity = parity;
            DataBits = dataBits;
            this.StopBits = stopBits;
            _sp = new SerialPort();
            _sp.NewLine = EndLineChar;
            
        }

        /// <summary>
        /// Sets up the serial port, with the wanted configuration. 
        /// Then opens the serial port, and listens for data
        /// </summary>
        /// <returns>true if connection is established</returns>
        public bool Start()
        {
            _sp.PortName = COMPort;
            _sp.BaudRate = BaudRate;
            _sp.Parity = this.Parity;
            _sp.DataBits = DataBits;
            _sp.StopBits = this.StopBits;
            
            try
            {
                if (!_sp.IsOpen)
                {
                    _sp.Open();
                    Online = true;
                    _sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
                }
                return true;
            }
            catch (Exception)
            {
                Online = false;
                return false;
            }
        }

        /// <summary>
        /// Closes the connection
        /// </summary>
        /// <returns>true if connection is stopped</returns>
        public bool Stop()
        {
            _sp.DataReceived -= sp_DataReceived;

            if (_sp.IsOpen)
            {
                //sp.DiscardInBuffer();
                try
                {

                    _sp.Close();
                    Online = false;
                }
                catch (Exception)
                {
                    
                    
                }
                
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Availible COM port names
        /// </summary>
        /// <returns>String array with names</returns>
        public string[] GetAvailibleCOMPorts()
        {
            return SerialPort.GetPortNames();
        }

        /// <summary>
        /// Write a command on the serial port, if its opened
        /// </summary>
        /// <param name="command">Command - se documentation for protocol</param>
        public void SendCommand(string command)
        {
            if (_sp.IsOpen)
            {
                try
                {
                    _sp.Write(command);
                }
                catch (TimeoutException)
                {
                    Stop();                    

                }

            }
            
        }

        /// <summary>
        /// Data received event handler, occurs when there is data availble on the serial port
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            
            try
            {
                string msg = _sp.ReadLine();
                try
                {
                    if (checkChecksum(msg))
                    {
                        string[] data = msg.Split('$');

                        data = data[0].Split(',');

                        try
                        {
                            _diys.AccAngle = Int32.Parse(data[0]) / 100;
                            _diys.GyroAnglePrSec = Int32.Parse(data[1]) / 100;
                            _diys.ApproxAngle = Int32.Parse(data[2]) / 100;
                            _diys.PWM = Int32.Parse(data[3]);
                            _diys.Steering = Int32.Parse(data[4]);
                        }
                        catch (Exception)
                        {
                        }
                    }
                }
                catch { }

                if (OnInputChanged != null)
                    OnInputChanged(msg);

                _sp.DiscardInBuffer();
            }
            catch
            {
            }
        }

        #region private helpers
        /// <summary>
        /// Checks the checksum for a received package
        /// </summary>
        /// <param name="msg">data to be checked</param>
        /// <returns>true if checksum is OK</returns>
        private bool checkChecksum(string msg)
        {
            string[] values = msg.Split('$');

            int calcChecksum = 0;
            int realChecksum = -1;

            if (values.Length == 2)
            {
                calcChecksum = CalcChecksum(values[0]);

                try
                {
                    realChecksum = int.Parse(values[1].Trim());
                }
                catch (Exception ex)
                {
                    //NewErrorMessage("Error parsing CRC: " + ex.Message);

                }

            }

            return calcChecksum == realChecksum;

        }

        /// <summary>
        /// Calculates checksum
        /// </summary>
        /// <param name="msg">data to be calculated on</param>
        /// <returns>checksum as int value</returns>
        private int CalcChecksum(string msg)
        {
            int calcChecksum = 0;

            foreach (char item in msg.ToCharArray())
            {
                calcChecksum += (byte)item;
            }

            return calcChecksum;

        }
        #endregion
    }
}
